/*
 * Author: vdaras
 */

#include "physics/EntityContactInfo.h"
#include "physics/ParticleSystem.h"
#include "physics/PhysicsManager.h"
#include "AnimationLogic.h"
#include "Camera.h"
#include "DrawLogic.h"
#include "EntityManager.h"
#include "global_script_functions.h"
#include "Logger.h"
#include "LuaScriptState.h"
#include "ServiceLocator.h"
#include "StateStack.h"
#include "State.h"
#include "audio/AudioSource.h"
#include "audio/StreamSource.h"
#include "Globals.h"
#include <boost/variant.hpp>


using physicsSystem::PhysicsManager;

/**
 * This routine is used by scripts in order to spawn permanet entities in the
 * game. If we just instansiated an Entity object in script it would be killed
 * when all it's references went out of scope.
 *
 * @param type - type of the entity
 * @param x - spawn x coordinate
 * @param y - spawn y coordinate
 * @param layer - rendering layer to be spawned on.
 */

Entity *SpawnEntity(const std::string &entityType, const std::string &id, int x, int y, const std::string &layer)
{
    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );

    State *currState = stateStack->Top( );

    EntityManager *entManager = currState->GetEntityManager( );

    Entity* spawned = entManager->RequestEntityImidAt( entityType, x, y );

    if(!spawned)
        return NULL;
    
    spawned->SetID( id );

    DrawLogic *drawLogic = spawned->GetAnimationLogic( );
    if( drawLogic != NULL )
    {
        RenderingManager *renderer = currState->GetRenderingManager( );

        renderer->RegisterDrawable( drawLogic, layer );
    }

    PhysicsLogic *physicsLogic = spawned->GetPhysicsLogic( );
    if( physicsLogic != NULL )
    {
        PhysicsManager *physicsManager = currState->GetPhysicsManager( );

        physicsManager->AddPhysicsLogic( physicsLogic );
    }

    spawned->InitializeScripts( );

    return spawned;
}

/**
 * This function is used by scripts in order to create a aparticle effect.
 * @param type - The type of the effect to spanw.
 * @param position - The position of this effect.
 * @param oneTime - If true the effect will play only once.
 * @param layer - rendering layer to spawn particles on
 * @return  the id of the emiter that was created for this effect. Usefull if oneTime is set to false.
 */

int SpawnParticles(const std::string& type, const Math::Vector2F& position, const Math::Vector2F& direction, bool oneTime, const std::string& layer)
{
    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );

    State *currState = stateStack->Top( );

    PhysicsManager* pManager = currState->GetPhysicsManager( );

    if( !pManager->IsParticleSystemLoaded( type ) )
    {
        RenderingManager *renderer = currState->GetRenderingManager( );

        if( !renderer->LayerExists( layer ) )
        {
            renderer->AddLayer( layer, 1.0f );
        }

        renderer->RegisterDrawable( pManager->LoadParticleSystem( type, true ), layer );
    }

    physicsSystem::ParticleSystem* pSystem = pManager->GetParticleSystem( type );

    pSystem->SetActive( true );

    return pSystem->AddPrototypedEmiterAt( position, direction, oneTime );
}

/**
 * 
 * @param name
 * @param position
 */
void PlaySound(const std::string& name/*, const Math::Vector2F position*/)
{
    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );
    State *currState = stateStack->Top( );
    al::AudioSource* source = currState->GetAudioManager( )->CreateSource( );
    source->Load( name.c_str( ) );
    source->Play( );
}

/**
 * Plays the specified music file in loop.
 * @param name
 */
void PlayMusic(const std::string& name)
{
    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );
    State *currState = stateStack->Top( );
    //its static temporally till streams and sounds are exported properly into AS.
    static al::StreamSource* stream = currState->GetAudioManager( )->CreateStream( );
    stream->Prepare((Globals::GetInstance()->GetPath("Music") + name).c_str());
    stream->Play();
    stream->SetNumOfLoops(AL_LOOP_FOREVER);
}

/**
 * Returns the requested entity. NULL if not found.
 * @param name
 * @return 
 */
Entity *FindEntity(const std::string& name)
{
    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );
    State *currState = stateStack->Top( );
    return currState->GetEntityManager( )->FindEntity(name);
}

/**
 * Returns a handle to the current camera service.
 * 
 * @return a pointer to a camera.
 */

Camera *GetCamera()
{
    return ServiceLocator< Camera >::GetService( );
}

/**
 * Loads a key mapping from the xml file keymap.xml.
 *
 * @param tag: the xml node in keympap.xml that contains the mapping.
 */


void LoadKeyAssociations(const std::string& tag)
{
    KeyMapper *currentMapper = ServiceLocator< KeyMapper >::GetService( );

    currentMapper->ReadFile( tag.c_str( ) );
}

/**
 * Pushes a script state to the state stack.
 *
 * @param scriptStateClass: name of the angelscript ScriptState class.
 */

void PushState(const std::string& scriptStateClass)
{
    EngineCore *engineCore = ServiceLocator< EngineCore >::GetService( );
    
    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );
    
    LuaScriptState *toPush = new LuaScriptState( engineCore, scriptStateClass );
    
    stateStack->Push( toPush );
    
    toPush->Create( );
}

/**
 * Pops the state stack.
 */

void PopStateStack()
{
    StateStack *stateStack = ServiceLocator< StateStack >::GetService( );

    stateStack->SafePop( );
}

/**
 * Proxy method used to extract entity contact info from a physical logic object.
 * Used in order to surpass the triangle of independence between Entity PhysicsLogic and EntityContactInfo
 * @param logic
 * @return 
 */
physicsSystem::PhysicsInternals::EntityContactInfo* GetNextContact(PhysicsLogic* logic)
{
    physicsSystem::PhysicsInternals::EntityContactInfo* cinfo = logic->GetNextContact( );
    return(cinfo );
}

/**
 * Prints a string to the standard output for scripting.
 *
 * @param toPrint - string to print
 */

void Print(const std::string &toPrint)
{
    LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_NOTE ) << toPrint;
}

/**
 * Prints an integer to the standard output for scripting.
 *
 * @param toPrint - integer to print
 */

void PrintInt(int toPrint)
{
    LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_NOTE ) << toPrint;
}

/**
 * Prints a bool to the standard output.
 *
 * @param toPrint - bool to print
 */

void PrintBool(bool toPrint)
{
    if( toPrint )
        LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_NOTE ) << "true";
    else
        LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_NOTE ) << "false";
}

/**
 * Prints an unsigned integer to the standard output for scripting.
 *
 * @param toPrint - unsigned integer to print
 */

void PrintUnsigned(Uint32 toPrint)
{
    LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_NOTE ) << toPrint;
}

/**
 * Prints a float or a double to the standard output for scripting.
 *
 * @param toPrint - number to print
 */

void PrintReal(double toPrint)
{
    LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_NOTE ) << toPrint;
}

/**
 * Prints a vector to the standard output for scripting.
 *
 * @param toPrint - number to print
 */

void PrintVector(const Math::Vector2F& toPrint)
{
    LOG( Logger::CHANNEL_SCRIPTING, LogFileStream::LEVEL_NOTE ) << toPrint;
}


/**
 * Returns a pointer to the resource manager.
 */

ResourceManager *GetResourceManager()
{
    return ResourceManager::GetInstance( );
}
